package com.basker.pisces.data;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.sql.DataSource;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NamedThreadLocal;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.jdbc.datasource.ConnectionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.basker.pisces.config.ApplicationConfig;
import com.basker.pisces.core.datasource.DbInfo;
import com.basker.pisces.core.datasource.DbType;
import com.basker.pisces.core.datasource.IDataSourceManager;
import com.basker.pisces.exception.ExceptionFactory;

//import io.seata.rm.datasource.DataSourceProxy;

/**
 * 动态数据源，它是单例的，会从spring容器中加载所有的datasouce实例
 *
 * @author hangwen
 */
public class DynamicDataSource extends AbstractDataSource implements InitializingBean, IDataSourceManager {

    private static final ThreadLocal<String> CONTEXT_DATASOURCE_NAME = new NamedThreadLocal<>("Context DataSouce Name");
    private static final String DEFAULT_NAME = "default";

    private String defaultDataSourceName = null;
    private DataSource defaultDataSource = null;
    private Map<String, DataSource> dataSources = new HashMap<>();
    private Map<String, DbInfo> dataSourceInfos = new HashMap<>();

    @Autowired(required = false)
    private Map<String, DataSource> autowiredDataSources;

    private AtomicBoolean loaded = new AtomicBoolean(false);

    @Autowired
    private ApplicationConfig appConfig;

//	private final String SEATA_DATASOURCE_CLASS_NAME = "io.seata.rm.datasource.DataSourceProxy";

    @Override
    public void afterPropertiesSet() throws Exception {
        this.loadDataSources();
        this.checkDefaultSouce();
    }

    @Override
    public Connection getConnection() throws SQLException {
        return this.getDataSource().getConnection();
    }

    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return this.getDataSource().getConnection(username, password);
    }

    @Override
    public DbInfo getDataSourceInfo(String dataSourceName) {
        String dataSourceName2use = dataSourceName;
        if (StringUtils.isEmpty(dataSourceName2use)) {
            dataSourceName2use = this.defaultDataSourceName;
        }

        return this.dataSourceInfos.get(dataSourceName2use);
    }

    @Override
    public String getDefaultDataSourceName() {
        return this.defaultDataSourceName;
    }

    @Override
    public void removeContextDataSourceName() {
        CONTEXT_DATASOURCE_NAME.remove();
    }

    @Override
    public void setContextDataSourceName(String dataSourceName) {
        String dataSourceName2use = dataSourceName;
        if (StringUtils.isEmpty(dataSourceName2use)) {
            dataSourceName2use = this.defaultDataSourceName;
        }

        // 如果已经在事务内，那么不允许切换数据源！
        ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(this);
        if (conHolder != null && conHolder.isSynchronizedWithTransaction()) {
            String currentDataSourceName = CONTEXT_DATASOURCE_NAME.get();
            if (StringUtils.isEmpty(currentDataSourceName)) {
                currentDataSourceName = this.defaultDataSourceName;
            }

            if (!currentDataSourceName.equals(dataSourceName2use)) {
                throw ExceptionFactory.createRuntimeException(
                        "pisces-jdbc.DynamicDataSource.setContextDataSourceName-001",
                        "can not set ContextDataSourceName in an opened transaction scope!");
            }

            return;
        }

        CONTEXT_DATASOURCE_NAME.set(dataSourceName2use);
    }

    protected final DataSource getDataSource() {
        Object dataSourceName = this.getDataSourceName();
        if (dataSourceName == null || dataSourceName.toString().length() == 0) {
            return this.defaultDataSource;
        }

        if (dataSources == null || dataSources.size() == 0) {
            throw ExceptionFactory.createRuntimeException("pisces-jdbc.DynamicDataSource.getDataSource-001",
                    "DynamicDataSource has not loaded!");
        }

        DataSource dataSource = dataSources.get(dataSourceName);
        if (dataSource == null) {
            throw ExceptionFactory.createRuntimeException("pisces-jdbc.DynamicDataSource.getDataSource-002",
                    "dataSource:{0} is not exist!", dataSourceName);
        }

        return dataSource;
    }

    protected String getDataSourceName() {
        return CONTEXT_DATASOURCE_NAME.get();
    }

    synchronized boolean addDataSource(String dataSourceId, DataSource dataSource, boolean isDefault)
            throws SQLException {
        Assert.hasText(dataSourceId, "parameter 'dataSourceId' is required");
        Assert.notNull(dataSource, "parameter 'dataSource' is required");

        if (dataSources.containsKey(dataSourceId)) {
            return false;
        }

        // 连接数据库，获取数据库的类型
        try (Connection connection = dataSource.getConnection()) {
            String dbName = connection.getCatalog();
            String databaseType = connection.getMetaData().getDatabaseProductName();
            DbType dbType = DbType.fromDatabaseProductName(databaseType);

            dataSourceInfos.put(dataSourceId, new DbInfo(dataSourceId, dbName, dbType));
        } catch (SQLException e) {
            throw e;
        }

        dataSource = wrapperSeataDataSourceIfNecessary(dataSource);

        // 处理默认数据源
        if (isDefault) {
            this.setDefaultDataSource(dataSourceId, dataSource);
        }

        dataSources.put(dataSourceId, dataSource);

        return true;
    }

    private String resolveDefaultDataSourceName() {
        String defaultName = this.appConfig.getString("pisces.datasource.default");
        if (StringUtils.isEmpty(defaultName)) {
            defaultName = DEFAULT_NAME;
        }

        return defaultName;
    }

    boolean exist(String dataSourceId) {
        return dataSources != null && dataSources.containsKey(dataSourceId);
    }

    synchronized boolean removeDataSouce(String dbId) {
        if (dataSources == null) {
            return false;
        }

        DataSource dataSource = dataSources.remove(dbId);
        return dataSource != null;
    }

    void setDefaultDataSource(String defaultDataSourceName, DataSource defaultDataSource) {
        this.defaultDataSourceName = defaultDataSourceName;
        this.defaultDataSource = defaultDataSource;
    }

    private void checkDefaultSouce() {
        if (this.defaultDataSource == null) {
            throw ExceptionFactory.createRuntimeException("pisces-jdbc.DynamicDataSource.checkDefaultSouce-001",
                    "No DataSource is named with 'default'!");
        }
    }

    private void loadDataSources() throws SQLException {
        if (!loaded.compareAndSet(false, true)) {
            return;
        }

        if (CollectionUtils.isEmpty(autowiredDataSources)) {
            return;
        }

        String theDefault = this.resolveDefaultDataSourceName();

        // 如果只有一个datasouce，它就是默认的
        if (autowiredDataSources.size() == 1) {
            Entry<String, DataSource> next = autowiredDataSources.entrySet().iterator().next();
            theDefault = next.getKey();
        }

        for (Entry<String, DataSource> entry : autowiredDataSources.entrySet()) {
            boolean isDefault = theDefault.equals(entry.getKey());
            this.addDataSource(entry.getKey(), entry.getValue(), isDefault);
        }
    }

    private DataSource wrapperSeataDataSourceIfNecessary(DataSource dataSource) {
        // seata不是必须引入的，所以需要运行时判断
//		if (ClassUtils.getClass(SEATA_DATASOURCE_CLASS_NAME, false) != null) {
//			return new DataSourceProxy(dataSource);
//		}

        return dataSource;
    }
}
